前言
关于java的读写锁 ReentrantReadWriteLock,在jdk1.8之后引入了 StampedLock。
ReentrantReadWriteLock
ReentrantReadWriteLock是基于AQS同步队列实现的读写锁,其允许读操作之间不阻塞,写操作与写操作、读操作都是阻塞的,即如果要获取写锁,必须保证写锁和读锁都没有被占用。ReentrantReadWriteLock的读锁是不能被升级为写锁,但是写锁是可以降为读锁。
使用实例
1 | class Count { |
官方实例
1 | class CachedData { |
部分源码分析
接下来分析其获取读写锁和释放读写锁的源码分析,如果看过AQS同步队列和ReentrantLock的源码,那么读接下来的代码会很轻松,AQS同步队列和ReentrantLock的知识不再做介绍了。ReentrantReadWriteLock的读锁对应于AQS中的共享锁,而写锁对应AQS中的独占锁。
同ReentrantLock相同,ReentrantReadWriteLock也是使用内部类Sync继承了AQS,通过继承Sync实现了公平锁和非公平锁。读写锁可以使用这两种方式获取锁,默认为非公平锁。
1 | abstract static class Sync extends AbstractQueuedSynchronizer { |
写锁的获取
写锁的获取是直接调用了AQS的acquire方法,AQS使用了模板方法,实际是通过Sync中的tryAcquire的方法判断获取写锁是否成功
1 | protected final boolean tryAcquire(int acquires) { |
在c != 0的情况下,说明读锁或者写锁其中一个或者两个被线程占有,当w==0说明,有线程占有读锁,这时就算当前获取锁的线程就是占有读锁的线程,也会返回false,只有w!=0(即当前有线程占有写锁),并且该线程是就是当前获取锁的线程才允许获取写锁。这说明线程的读锁是无法升级成写锁的,写锁和所有的读锁阻塞,即使是自身占有写锁,而写锁是支持重入的。
写锁的释放
同样,写锁的释放是直接调用了AQS的release方法,AQS使用了模板方法,实际是通过Sync中的tryRelease的方法判断释放写锁是否成功
1 | protected final boolean tryRelease(int releases) { |
每次释放锁,获得写锁的次数减1,如果为0,则释放写锁,这ReentrantLock的可重入方法实相同
读锁的获取
读锁的获取是直接调用了AQS的acquireShared方法,AQS使用了模板方法,实际是通过Sync中的tryAcquireShared的方法判断获取写锁是否成功
1 | protected final int tryAcquireShared(int unused) { |
在获取读锁的过程中是不需要判断重入的,获取读锁的过程中首先需要判断有写锁的占用,接着需要判读是否需要阻塞:在公平锁下,还需要保证等待队列中不存在等待的线程,如果有则说明其中必定有获取写锁的进程存在,则当前获取读锁的线程就需要阻塞;在非公平锁下,为了防止造成饥饿现象,如果下一个线程是获取写锁的进程,则需要阻塞,否则可以直接获得读锁。最后获取锁失败则调用fullTryAcquireShared方法,其中要处理将写锁降级为读锁的可能。
1 | final int fullTryAcquireShared(Thread current) { |
读锁的释放
1 | protected final boolean tryReleaseShared(int unused) { |
注意
- 写锁可以降为读锁,即一个线程A获得写锁,其他线程是无法获得读锁的,但是A线程可以获得,写锁降级为读锁不需要判断等待队列是否有写锁在,不用遵循公平锁和非公平锁的原则
读锁是不用判断可重入的(没有必要),写锁是需要判断的
写锁是和所有的读锁互斥的,包括获取写锁的线程正在占有读锁